bet 1.0.3

Helps parsing and evaluating binary expression trees
Documentation

MIT Latest Version docs Chat on Miaou

A library building and preparing expressions, for example boolean expressions such as (A | B) & !(C | D | E), which can be executed on dynamic contents.

An expression is built by sequentially pushing the parts: parenthesis, operators, atoms (the "variables"). You do that by calling the push_operator, open_par, close_par and push_atom functions, which build the tree for you.

It can then be evaluated with the eval function which takes as parameters

  • a function which gives a value to an atom
  • a function which, given an operator and one or two values, gives a new value
  • a function deciding whether to short-circuit

Normal evaluation order is left to right but is modified with parenthesis.

bet is designed around separation of building, transformations, and evaluation, so that an expression can be efficiently applied on many inputs. bet is designed for very fast evaluation.

If you wonder whether bet could be applied to your problems, don't hesitate to come and discuss.

Known open-source usages

dysk

bet is used in dysk to filter filesystems.

Example: dysk -f '(type=xfs & remote=no) | size > 5T'.

Here, the atoms are type=xfs, remote=no, and size > 5T.

To parse such expression, the simplest solution is to first parse it with atoms being simple strings, then apply try_map_atoms on the tree to replace the strings with structs which can be efficiently evaluated on many entries.

Here's how it's done:

impl FromStr for Filter {
    type Err = ParseExprError;
    fn from_str(input: &str) -> Result<Self, ParseExprError> {

        // we start by reading the global structure
        let mut expr: BeTree<BoolOperator, String> = BeTree::new();
        for c in input.chars() {
            match c {
                '&' => expr.push_operator(BoolOperator::And),
                '|' => expr.push_operator(BoolOperator::Or),
                '!' => expr.push_operator(BoolOperator::Not),
                ' ' => {},
                '(' => expr.open_par(),
                ')' => expr.close_par(),
                _ => expr.mutate_or_create_atom(String::new).push(c),
            }
        }

        // then we parse each leaf
        let expr = expr.try_map_atoms(|raw| raw.parse())?;

        Ok(Self { expr })
    }
}

broot

In broot, bet enables composite queries on files.

For example, !lock&(carg|c/carg/) looks for files whose name or content contains "carg", but excluding files whose name contains "lock".

rhit

bet is used in rhit to filter log lines.

For example, with rhit -p 'y & !( \d{4} | sp | bl )', you get stats on hits on paths containing "y" but neither a 4 digits number, "sp", nor "bl".